package com.xhu.water_supply.web.utils;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import com.xhu.water_supply.dto.ResultEnum;
import com.xhu.water_supply.utils.DateUtils;
import com.xhu.water_supply.utils.UUIDUtils;
import com.xhu.water_supply.web.exception.JwtExpiredException;
import com.xhu.water_supply.web.exception.JwtInvalidException;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * @Description JWT工具类
 */
@Component
public class JwtComponent {
    /**
     * 获取默认的负载对象
     * @param subject       主体内容
     * @param userName      用户名
     * @param authorities   权限列表
     * @return
     */
    public PayloadDTO getDefaultPayloadDTO(String subject, String userName, List<String> authorities){
        return getDefaultPayloadDTO(subject, userName, authorities, 18000);
    }

    /**
     * 获取默认的负载对象,负载信息默认证书有效时间为18000秒
     * @param subject       主体内容
     * @param userName      用户名
     * @param authorities   权限列表
     * @param expSecond     有效时间，单位秒
     * @return
     */
    public PayloadDTO getDefaultPayloadDTO(String subject, String userName, List<String> authorities, int expSecond){
        Date now = new Date();
        Date exp = DateUtils.addDate(now, Calendar.SECOND, expSecond);
        return PayloadDTO.builder()
                .jId(UUIDUtils.getUUID32())
                .iat(now.getTime())
                .exp(exp.getTime())
                .administratorName(userName)
                .sub(subject)
                .authorities(authorities).build();
    }

    private PayloadDTO parseToPayloadDTO(JWSObject jwsObject, JWSVerifier jwsVerifier) throws JOSEException, JwtInvalidException {
        //1. 验证签名
        if(!jwsObject.verify(jwsVerifier)){
            throw new JwtInvalidException(ResultEnum.FAIL_TOKEN_VERIFY);
        }
        //2. 验证负载信息
        String payload = jwsObject.getPayload().toString();
        PayloadDTO payloadDTO = JSONUtil.toBean(payload, PayloadDTO.class);
        //3. 验证是否过期
        if(payloadDTO.getExp() < System.currentTimeMillis()){
            throw new JwtInvalidException(ResultEnum.FAIL_TOKEN_EXP);
        }
        //4. 返回负载信息
        return payloadDTO;
    }

    /**
     * 使用HMAC算法构建Token
     *
     * @param payloadDto 负载信息
     * @param secret     秘钥
     * @return 返回Token字符串
     */
    public String generateTokenByHmac(PayloadDTO payloadDto, String secret) throws JOSEException {
        //1 创建JWS头，设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();
        //2 将负载信息封装到Payload中
        Payload payload = new Payload(JSONUtil.toJsonStr(payloadDto));
        //3 创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        //4 创建HMAC签名器
        secret = SecureUtil.md5(secret);
        JWSSigner jwsSigner = new MACSigner(secret);
        //5 签名
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }

    /**
     * 验证HMAC Token
     *
     * @param token  token字符串
     * @param secret 密钥
     * @return 负载信息对象
     */
    public PayloadDTO verifyTokenByHmac(String token, String secret) throws ParseException, JOSEException, JwtInvalidException, JwtExpiredException {
        //1 从token中解析JWS对象
        JWSObject jwsObject = JWSObject.parse(token);
        //2 创建HMAC验证器
        secret = SecureUtil.md5(secret);
        JWSVerifier jwsVerifier = new MACVerifier(secret);
        //3 验证并获取负载信息
        return parseToPayloadDTO(jwsObject, jwsVerifier);
    }

    /**
     * 通过RSA生成token
     *
     * @param payloadDto 负载信息
     * @param rsaKey     签名Key
     * @return 返回Token字符串
     */
    public String generateTokenByRsa(PayloadDTO payloadDto, RSAKey rsaKey) throws JOSEException {
        //1 创建JWS头，设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build();
        //2 将负载信息封装到Payload中
        Payload payload = new Payload(JSONUtil.toJsonStr(payloadDto));
        //3 创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        //4 创建RSA签名器
        JWSSigner jwsSigner = new RSASSASigner(rsaKey, true);
        //5 签名
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }

    /**
     * 验证RSA Token
     *
     * @param token  token字符串
     * @param rsaKey 签名Key
     * @return 负载信息对象
     */
    public PayloadDTO verifyTokenByRsa(String token, RSAKey rsaKey) throws ParseException, JOSEException, JwtInvalidException, JwtExpiredException {
        //1 从token中解析JWS对象
        JWSObject jwsObject = JWSObject.parse(token);
        RSAKey publicRsaKey = rsaKey.toPublicJWK();

        //2 使用RSA公钥创建RSA验证器
        JWSVerifier jwsVerifier = new RSASSAVerifier(publicRsaKey);

        //3 验证并获取负载信息
        return parseToPayloadDTO(jwsObject, jwsVerifier);
    }
}